一个时间的几种不同方式的写法,导致输出的结果不一样:
console.log(new Date('2021-10-01'));
console.log(new Date('2021-10-1'));
console.log(new Date('2021/10/01'));
console.log(new Date('2021/10/1'));
console.log(new Date('2021-10-01 0:0:0'));
console.log(new Date('2021/10/01 0:0:0'));
在 Node.js 中的执行结果为:
2021-10-01T00:00:00.000Z // 与其他不同
2021-09-30T16:00:00.000Z
2021-09-30T16:00:00.000Z
2021-09-30T16:00:00.000Z
2021-09-30T16:00:00.000Z
2021-09-30T16:00:00.000Z
从上面的例子可以看出来,当使用 补0 形式的以 - 连接的日期字符串时,会认为传入的字符串是基于 UTC 时区的,而其他形式的会基于本地时区,导致第一种会比其他时间快8个小时,那原因是为什么呢?
来翻一下V8的源码,到 Date parse字符串的部分( https://github.com/v8/v8/blob/master/src/date/dateparser-inl.h#L72 ),可以看到在一开始先调用了 ParseES5DateTime 来处理符合 ES5 ISO 8601 格式的时间字符串。
ISO 8601 的时间字符串定义了两种时间格式,一种是 YYYY-MM-DD 格式的日期格式,另外一种是 YYYY-MM-DDTHH:MM:DD 格式的日期+时间格式,如果是日期格式,那么将会使用 UTC时区 来解析该参数,而如果是时间+日期的格式,则将会被作为 本地时区 处理,这也就解释了为什么 new Date('2021-10-01') 会变成UTC时区的时间。
从V8的实现中来看( https://github.com/v8/v8/blob/master/src/date/dateparser-inl.h#L344 ),在判断符合 ES5 ISO 8601 规范的日期格式时,通过调用 tz->Set(0) 将这个事件的时区偏移量设置为 UTC + 0。
在完成时间的解析处理后,调用 tz->Write 方法( https://github.com/v8/v8/blob/master/src/date/dateparser.cc#L99 )将时间偏移量写入结果数组(out),在其中判断了是否设置过时区(调用 TimeZoneComposer::Set 方法后会将其内部的 sign 属性设置为 1 或 -1,如果未调用过,那么 sign 的值为 kNone ),如果 `sign 设置过,那么就在结果数组的第7个值( out[DateParser::UTC OFFSET] )写入时区偏移量 ;否则,就会将 out[DateParser::UTC OFFSET] 设置为 NaN`。
而对于其他的时间格式,如果没有在传入的时间字符串中指定时区,那么就不会调用 TimeZoneComposer::Set 方法,在 ParseDateTimeString 方法的尾部处理时区时( https://github.com/v8/v8/blob/master/src/builtins/builtins-date.cc#L51 ),会判断 out[DateParser::UTC_OFFSET] 的值,如果是NaN,会根据本地时区来给时间附加一个偏移量,所以除了 YYYY-MM-DD 格式之外的其他形式时间字符串解析后的时区都变成了笔者所在的 UTC+8 。